module dungeon.hud;

import std.algorithm;
import std.array;
import std.string;

import derelict.opengl.gl;

import nade.ast;
import nade.loader;
import nade.hud;
import nade.color;
import nade.vector;
import nade.region;
import nade.primitive;
import nade.font;
import nade.clipper;

import dungeon.game;
import dungeon.character;
import dungeon.stats;
import dungeon.editor;


class Frame : HUD, IFrame {
	protected {
		Game				_game;
		Frame				_parent;
		string			_id, _name;
		Vector2i		_sizeMin = Vector2i(1, 1);
		Vector2i		_sizeMax = Vector2i(int.max, int.max);
		Frame[]			_children;
	}

	this(Game game, Frame parent = null)
	{
		_game = game;
		_parent = parent;
		if(_parent !is null) _parent.addChild(this);
	}

	override void render(const Region2 area)
	{
		auto scroll = scrollArea(area);

		renderFrame(area);

		glPushMatrix();
		glTranslatef(area.min.x - scroll.min.x, area.min.y + area.height - scroll.max.y, 0);
		super.render();
		glPopMatrix();

		version(Debug)
		{
			Color(1, 1, 0).render;
			renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));
		}
	}

	void renderFrame(const Region2 area)
	{
		version(Debug)
		{
			Color(0, 0, 1).render;
			renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));
		}
	}

	override bool click(Vector2 position)
	{
		return super.click(position);
	}

	Vector2i layout()
	{
		hide();
		auto result = _sizeMin;
		foreach(child; _children)
		{
			result = result.max(show(child, Region2(Vector2i.ZERO, child.layout)).max);
		}
		return result.min(_sizeMax);
	}

	override Vector2i sizeMin() { return _sizeMin; }
	override Vector2i sizeMax() { return _sizeMax; }
	override Region2 scrollArea(const Region2 area) { return Region2(Vector2i.ZERO, area.dimensions); }

	void addChild(Frame child) { _children ~= child; }
	Frame[] children() { return _children; }

	string id() { return _id; }
	string name() { return _name; }

	static void install()
	{
		Loader!(Frame).add("name",
			(ref Frame frame, AST ast, Object[string] state) { frame._id = ast[0].as!string; frame._name = ast[1].as!string; }
		);

		Loader!(Frame).add("size",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin = frame._sizeMax = Vector2i(ast[0].as!int, ast[1].as!int); }
		);
		Loader!(Frame).add("sizeMin",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin = Vector2i(ast[0].as!int, ast[1].as!int); }
		);
		Loader!(Frame).add("sizeMax",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMax = Vector2i(ast[0].as!int, ast[1].as!int); }
		);

		Loader!(Frame).add("width",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin.x = frame._sizeMax.x = ast[0].as!int; }
		);
		Loader!(Frame).add("widthMin",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin.x = ast[0].as!int; }
		);
		Loader!(Frame).add("widthMax",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMax.x = ast[0].as!int; }
		);

		Loader!(Frame).add("height",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin.y = frame._sizeMax.y = ast[0].as!int; }
		);
		Loader!(Frame).add("heightMin",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMin.y = ast[0].as!int; }
		);
		Loader!(Frame).add("heightMax",
			(ref Frame frame, AST ast, Object[string] state) { frame._sizeMax.y = ast[0].as!int; }
		);

		Loader!(Frame).add("window",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto window = new Window(frame._game, frame);
				load(window, ast, state);
			}
		);
		Loader!(Frame).add("row",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto row = new Row(frame._game, frame);
				load(row, ast, state);
			}
		);
		Loader!(Frame).add("column",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto column = new Column(frame._game, frame);
				load(column, ast, state);
			}
		);
		Loader!(Frame).add("text",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto text = new StaticText(frame._game, frame);
				load(text, ast, state);
			}
		);
		Loader!(Frame).add("stat",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto stat = new StatText(frame._game, frame);
				load(stat, ast, state);
			}
		);
		Loader!(Frame).add("inventory",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto inventory = new InventorySlot(frame._game, frame);
				load(inventory, ast, state);
			}
		);
		Loader!(Frame).add("equipment",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto equipment = new EquipmentSlot(frame._game, frame);
				load(equipment, ast, state);
			}
		);
		Loader!(Frame).add("listbox",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto listbox = new ListBox(frame._game, frame);
				load(listbox, ast, state);
			}
		);

		Loader!(Frame).add("tilesets",
			(ref Frame frame, AST ast, Object[string] state)
			{
				auto listbox = new EditorTilesets(frame._game, frame);
				load(listbox, ast, state);
			}
		);
	}
}


class Window : Frame {
	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override void renderFrame(const Region2 area)
	{
		Color.BLACK.render(0.5);
		renderFilledRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		Color.WHITE.render;
		renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		super.renderFrame(area);
	}

	override bool click(Vector2 position)
	{
		super.click(position);
		return true;
	}

	override Vector2i layout()
	{
		hide();
		auto result = Vector2i.ZERO.dup;
		foreach(child; _children)
		{
			result = result.max(show(child, Region2(Vector2i.ZERO, child.layout)).max);
		}
		_sizeMin = result;
		return result.min(_sizeMax);
	}

	override Region2 scrollArea(const Region2 area) { return Region2(Vector2i.ZERO, _sizeMin); }

	static void install()
	{
		Loader!(Window).add("show",
			(ref Window window, AST ast, Object[string] state)
			{
				if(window._parent !is null)
				{
					if(ast.length >= 4)
						window._parent.show(window, Region2(ast[0].as!int, ast[1].as!int, ast[2].as!int, ast[3].as!int));
					else
						window._parent.show(window, Vector2i(ast[0].as!int, ast[1].as!int));
				}
			}
		);
	}
}


class Layout : Frame {
	protected {
		Vector2i		_offset = Vector2i.ZERO;
		Vector2i		_padding = Vector2i.ZERO;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	static void install()
	{
		Loader!(Layout).add("offset",
			(ref Layout layout, AST ast, Object[string] state)
			{
				int ox, oy;

				if(ast.length >= 2)
					ox = ast[0].as!int, oy = ast[1].as!int;
				else if(ast.length >= 1)
					ox = oy = ast[0].as!int;
				else
					ox = oy = 0;

				layout._offset = layout._offset + Vector2i(ox, oy);
			}
		);
		Loader!(Layout).add("padding",
			(ref Layout layout, AST ast, Object[string] state)
			{
				int ox, oy, px, py;

				if(ast.length >= 4)
					ox = ast[0].as!int, oy = ast[1].as!int, px = ox + ast[2].as!int, py = oy + ast[3].as!int;
				else if(ast.length >= 3)
					ox = ast[0].as!int, oy = ast[1].as!int, px = ox + ast[2].as!int, py = oy*2;
				else if(ast.length >= 2)
					ox = ast[0].as!int, oy = ast[1].as!int, px = py = ox*2;
				else if(ast.length >= 1)
					ox = oy = ast[0].as!int, px = py = ox*2;
				else
					ox = oy = px = py = 0;

				layout._offset = Vector2i(ox, oy);
				layout._padding = Vector2i(px, py);
			}
		);
	}
}


class Column : Layout {
	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override Vector2i layout()
	{
		hide();
		auto position = Vector2i.ZERO.dup;
		auto result = Region2(_sizeMin, _sizeMin);
		foreach_reverse(child; _children)
		{
			const size = child.layout;
			result = result.merge(show(child, Region2(position, position + size)));
			position.y = position.y + size.y;
		}
		reposition(_offset - result.min);
		return (result.dimensions + _padding).min(_sizeMax);
	}
}


class Row : Layout {
	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override Vector2i layout()
	{
		hide();
		auto position = Vector2i.ZERO.dup;
		auto result = Region2(_sizeMin, _sizeMin);
		foreach(child; _children)
		{
			const size = child.layout;
			result = result.merge(show(child, Region2(position, position + size)));
			position.x = position.x + size.x;
		}
		reposition(_offset - result.min);
		return (result.dimensions + _padding).min(_sizeMax);
	}
}


class Text : Layout {
	enum Align { LEFT, RIGHT, CENTER };

	protected {
		int			_size = 14;
		Align		_align = Align.LEFT;
		Color		_color = Color.WHITE;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override void renderFrame(const Region2 area)
	{
		auto t = text;
		if(!t.empty)
		{
			switch(_align)
			{
				case Align.LEFT: font.print(area.min.x + _offset.x, area.min.y + _offset.y, t, _size, _color); break;
				case Align.RIGHT: font.printRight(area.max.x - _offset.x, area.min.y + _offset.y, t, _size, _color); break;
				case Align.CENTER: font.printCenter(area.center.x, area.min.y + _offset.y, t, _size, _color); break;
				default:
			}
		}

		super.renderFrame(area);
	}

	override Vector2i layout()
	{
		return _sizeMin = _sizeMin.max(Vector2i(cast(int)font.textWidth(text, _size) + _padding.x, _size + _padding.y));
	}

	string text() { return ""; }

	static void install()
	{
		Loader!(Text).add("align",
			(ref Text text, AST ast, Object[string] state)
			{
				switch(ast[0].as!string)
				{
					case "left": text._align = Align.LEFT; break;
					case "right": text._align = Align.RIGHT; break;
					case "center": text._align = Align.CENTER; break;
					default:
				}
			}
		);
		Loader!(Text).add("size",
			(ref Text text, AST ast, Object[string] state)
			{
				text._size = ast[0].as!int;
			}
		);
		Loader!(Text).add([ "color", "colour" ],
			(ref Text text, AST ast, Object[string] state)
			{
				text._color = Color(ast[0].as!real, ast[1].as!real, ast[2].as!real, (ast.length >= 4) ? ast[3].as!real : 1);
			}
		);
	}
}


class StaticText : Text {
	protected {
		string		_text = "";
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override string text() { return _text; }
	string text(string rhs) { return _text = rhs; }

	static void install()
	{
		Loader!(StaticText).add(0,
			(ref StaticText text, AST ast, Object[string] state)
			{
				text._text = ast.as!string;
			}
		);
	}
}


class StatText : Text {
	protected {
		uint		_handle;
		int			_decimals;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);

		_align = Align.RIGHT;
	}

	override Vector2i layout()
	{
		return _sizeMin = _sizeMin.max(Vector2i(cast(int)font.textWidth("AAAAAA", _size), _size));
	}

	override string text() { return format("%0.*f", _decimals, _game.hudCharacter.stat(_handle)); }

	static void install()
	{
		Loader!(StatText).add(0,
			(ref StatText text, AST ast, Object[string] state)
			{
				text._handle = StatInfo.get(ast.as!string).handle;
			}
		);

		Loader!(StatText).add(1,
			(ref StatText text, AST ast, Object[string] state) { text._decimals = ast.as!int; }
		);
	}
}


class InventorySlot : Frame {
	protected {
		uint		_index;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);

		_sizeMin = _sizeMax = Vector2i(32, 32);
	}

	override void renderFrame(const Region2 area)
	{
		auto slot = _game.hudCharacter.inventory.slots.ptr + _index;
		if(slot.item !is null)
		{
			glPushMatrix();
			((area.min + area.max)*0.5).translate;
			slot.item.sprite.render(Vector2(32, 32));
			glPopMatrix();

			font.print(area.min.x + 1, area.max.y - 13, format("%d", slot.count), 14);
		}

		Color.GREY.render;
		renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		super.renderFrame(area);
	}

	override Vector2i layout()
	{
		return _sizeMin;
	}

	override bool click(Vector2 position)
	{
		auto slot = _game.hudCharacter.inventory.slots.ptr + _index;
		if(slot.item !is null  &&  slot.item.use(_game.hudCharacter))
		{
			if(--slot.count <= 0) slot.item = null;
		}

		return true;
	}

	static void install()
	{
		Loader!(InventorySlot).add(0,
			(ref InventorySlot slot, AST ast, Object[string] state)
			{
				slot._index = ast.as!uint - 1;
			}
		);
	}
}


class EquipmentSlot : Frame {
	protected {
		string		_name;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);

		_sizeMin = _sizeMax = Vector2i(32, 32);
	}

	override void renderFrame(const Region2 area)
	{
		auto slot = _game.hudCharacter.loadout.slot(_name);
		if(slot.item !is null)
		{
			glPushMatrix();
			((area.min + area.max)*0.5).translate;
			slot.item.sprite.render(Vector2(32, 32));
			glPopMatrix();
		}

		Color.GREY.render;
		renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		super.renderFrame(area);
	}

	override Vector2i layout()
	{
		return _sizeMin;
	}

	override bool click(Vector2 position)
	{
		auto slot = _game.hudCharacter.loadout.slot(_name);
		if(slot.item !is null) slot.item = null;

		return true;
	}

	static void install()
	{
		Loader!(EquipmentSlot).add(0,
			(ref EquipmentSlot slot, AST ast, Object[string] state)
			{
				slot._name = ast.as!string;
			}
		);
	}
}


class ListBox : Frame {
	protected {
		int				_itemHeight = 14;
		Color			_itemColor = Color.WHITE;
		Color			_highlightColor = Color.GREY;
		Color			_borderColor = Color.WHITE;
		Region2i	_scrollArea;
	}

	this(Game game, Frame parent = null)
	{
		super(game, parent);
	}

	override void renderFrame(const Region2 area)
	{
		_borderColor.render;
		renderRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		super.renderFrame(area);
	}

	override Vector2i layout()
	{
		hide();
		auto position = Vector2i.ZERO.dup;
		auto result = Region2(_sizeMin, _sizeMin);
		foreach_reverse(child; _children)
		{
			const childSize = child.layout;
			const size = Vector2i(_sizeMin.x, childSize.y);
			result = result.merge(show(child, Region2(position, position + size)));
			position.y = position.y + size.y;
		}
		_scrollArea = Region2i(Vector2i.ZERO, result.dimensions);
		auto actualSize = result.dimensions.min(_sizeMax) + Vector2i(2, 2);
		reposition(Vector2i(1, -1));
		return actualSize;
	}

	override Region2 scrollArea(const Region2 area) { return _scrollArea; }

	static void install()
	{
		Loader!(ListBox).add("itemHeight",
			(ref ListBox listbox, AST ast, Object[string] state)
			{
				listbox._itemHeight = ast[0].as!int;
			}
		);
		Loader!(ListBox).add([ "color", "colour", "itemColor", "itemColour" ],
			(ref ListBox listbox, AST ast, Object[string] state)
			{
				listbox._itemColor = Color(ast[0].as!real, ast[1].as!real, ast[2].as!real, (ast.length >= 4) ? ast[3].as!real : 1);
			}
		);
		Loader!(ListBox).add([ "highlightColor", "highlightColour" ],
			(ref ListBox listbox, AST ast, Object[string] state)
			{
				listbox._highlightColor = Color(ast[0].as!real, ast[1].as!real, ast[2].as!real, (ast.length >= 4) ? ast[3].as!real : 1);
			}
		);
		Loader!(ListBox).add([ "borderColor", "borderColour" ],
			(ref ListBox listbox, AST ast, Object[string] state)
			{
				listbox._borderColor = Color(ast[0].as!real, ast[1].as!real, ast[2].as!real, (ast.length >= 4) ? ast[3].as!real : 1);
			}
		);

		Loader!(ListBox).add("item",
			(ref ListBox listbox, AST ast, Object[string] state)
			{
				auto item = new ListBoxItem(listbox._game, listbox, listbox._itemHeight, listbox._itemColor, listbox._highlightColor);
				load(item, ast, state);
			}
		);
	}
}


class ListBoxItem : StaticText {
	protected {
		Color		_highlightColor = Color.GREY;
	}

	this(Game game, ListBox parent, int itemHeight, Color itemColor, Color highlightColor)
	{
		super(game, parent);

		int padding = cast(int)(itemHeight*0.1);
		_offset = Vector2i(padding, padding);
		_padding = Vector2i(padding*2, padding*2);
		_size = itemHeight - padding;
		_color = itemColor;
		_highlightColor = highlightColor;
	}

	override void renderFrame(const Region2 area)
	{
		// _highlightColor.render;
		// renderFilledRectangle(area.min.as!(Vector2), area.max.as!(Vector2));

		super.renderFrame(area);
	}

	static void install()
	{
	}
}
